除非是一个新项目,否则使用 Flutter 重写整个项目是不太现实的,这就需要我们将 Flutter 集成到原有的项目中去。
下面就讲解一些原有项目如何集成 Flutter,以及原生系统和 Flutter 之间是如何通信的。
这个必须提前说明,以下内容的环境为:
Flutter (Channel beta, v1.17.0, on Microsoft Windows [Version 10.0.18362.836], locale zh-CN)
Flutter 版本是 v1.17.0
写在前面
你可能会看无数的文章,它们都会给你讲一系列的方法,什么在 XML 文件中设置一个容器,然后调用容器的 addView 方法将 FlutterView 插入到原生页面里去之类的。
这些都是 1.12 之前的写法!!!现在已经完全废弃了!!!让人比较恶心的是,在官网上并没有说明这点,我一通搜索一通操纵然后一通失败一通感到万分沮丧甚至开始怀疑人生,直到在 flutter 的 github 上一篇文章的最后一段找到了相关内容:
The deprecated io.flutter.facade.Flutter class has a factory method called createView(...). This method is deprecated, along with all other code in the io.flutter.facade package.
Flutter does not currently provide convenient APIs for utilizing Flutter at the View level, so the use of a FlutterView should be avoided, if possible. However, it is technically feasible to display a FlutterView, if required. Be sure to use io.flutter.embedding.android.FlutterView instead of io.flutter.view.FlutterView. You can instantiate the new FlutterView just like any other Android View. Then, follow instructions in the associated Javadocs to display Flutter via a FlutterView.
大致意思就是,Flutter 已经弃用了 View 级别的嵌套了!!也就是说,别想着把 Flutter 当作是 Android 布局中的一个 View 了!!
准备工作
先创建一个 Android 工程作为宿主,项目名就叫:AndroidWithFlutter,创建过程略...
紧接着创建一个 Flutter Module,Flutter 将以一个模块的形式加入到已有的项目中,创建 Flutter 模块的方式有两种:
Android Studio 创建 Flutter 模块
Flie —— New —— New Module:
选择 Flutter Module:
接下来就是填写 module 的一些信息,照实填写即可,图略
推荐使用 Android Studio 来创建模块,这样系统会自动将 Flutter 模块配置到 Android 项目中:
Android 项目的 settings.gradle:
include ':app' //以下内容为系统自动添加的 setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir, '../flutter_module/.android/include_flutter.groovy' ))
setBinding 处会飘红,无需理会。
app build.gradle:
android { ... // 以下为系统新增,如果没有,可手动添加 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // 以下为系统新增,如果没有,可手动添加 implementation project(path: ':flutter') }
这样,Flutter 模块就添加到已有的项目中了。
命令行创建 Flutter 模块
在 Android Studio 的 Terminal
中执行如下命令:
flutter create -t module --org com.lixyz.fluttermodule flutter_module
含义就不多解释了,最后两个参数分别是包名和模块名称,接下来需要我们手动去配置上面由 Android Studio 自动生成的那些内容:
Android 项目的 settings.gradle:
... setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir, '../flutter_module/.android/include_flutter.groovy' ))
setBinding 处会飘红,无需理会。
app build.gradle:
android { ... // 以下为系统新增,如果没有,可手动添加 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // 以下为系统新增,如果没有,可手动添加 implementation project(path: ':flutter') }
添加完之后记得 Sync Now
至此,两种方式创建 Flutter 模块的工作就完成了。
使用 Flutter 模块
原有项目想要使用 Flutter 模块,有两种方式:
- 将 Flutter 模块打包成 arr 文件然后引入到现有工程,这里略过步骤,可以看这里:option-a---depend-on-the-android-archive-aar
- 直接使用模块源代码(在准备工作中我们添加到配置文件中的内容就是为了这个)
原生 Activity 转跳到 Flutter 页面
将 FlutterActivity 添加到 Android 工程的 AndroidManifest 文件中:
<activity android:name="io.flutter.embedding.android.FlutterActivity" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" />
启动一个 Flutter 页面
Flutter 提供了以下跳转到 Flutter 页面的方法:
直接跳转到 Flutter 的首页:
startActivity(FlutterActivity.createDefaultIntent(MainActivity.this));
上面的方法会跳转到 Flutter 工程的首页,可以看到 FlutterActivity 的 createDefaultIntent 返回的是一个 Intent 对象。
跳转到指定页面
startActivity( FlutterActivity .withNewEngine() .initialRoute("First") .build(MainActivity.this) );
通过 initialRoute 来指定需要跳转到哪个 Flutter 页面,这个目标页面需要在 Flutter 中通过
routes
来提前声明。注意:在上面的方法中,使用
withNewEngine
方法,这个方法会创建一个新的 Engine 来打开这个 Flutter 页面,引擎对象的创建需要时间,这就必然会带来不必要的耗时,所以我们可以使用已经提前创建好的缓存引擎,来提高响应速度,一般情况下我们在 Application 中缓存引擎:public class MyApplication extends Application { FlutterEngine flutterEngine; @Override public void onCreate() { super.onCreate(); flutterEngine = new FlutterEngine(this); flutterEngine.getNavigationChannel().setInitialRoute("Second"); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); FlutterEngineCache .getInstance() .put("custom_engine_id", flutterEngine); } }
然后使用缓存引擎:
startActivity( FlutterActivity .withCachedEngine("custom_engine_id") .build(MainActivity.this) );
需要注意的是,使用缓存引擎的话,需要提前指定初始 Route,即使在 Flutter 中也设置了
initialRoute
属性,也会以缓存引擎中指定的为主。
Flutter 页面跳转到原生 Activity
按照官方网站的教程是无办法完成的,会抛出
MissingPluginException
,恶心死了,网上的文章也是这个抄那个全网不管对错一通抄,没一个能跑的通的,直到发现了一个大神的博客,才算是搞定了。
Flutter 跳转到原生需要用到 MethodChannel,具体思路是,调用原生系统中的一个方法,在方法中执行一个 startActivity 来达到跳转的目的。关于 MethodChannel,回头单开一篇文章吧。
按照官方的文档操作是有问题的,以下方式是经过验证可行的
创建一个 FlutterActivity 的子类:
import android.content.Intent;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel;
public class BridgeActivity extends FlutterActivity {
private static final String CHANNEL_NAME = "TEST_CHANNEL_NAME"; public static Object shareObject; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); //config a method channel new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL_NAME) .setMethodCallHandler((call, result) -> { switch (call.method) { case "startNativeActivity": Intent intent = new Intent(BridgeActivity.this,NativeActivity.class); String content = call.argument("Content"); intent.putExtra("Content",content); startActivity(intent); break; default: result.notImplemented(); break; } }); } public static FlutterActivity.NewEngineIntentBuilder withNewEngine() { return new MyNewEngineIntentBuilder(BridgeActivity.class); } public static FlutterActivity.CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) { return new MyCachedEngineIntentBuilder(BridgeActivity.class, cachedEngineId); } /** * 之所以"空"继承{@link io.flutter.embedding.android.FlutterActivity.NewEngineIntentBuilder},因为内建在Intent创建时静态的写定了 * FlutterActivity.class,从而导致在此继承的子类中重写的configureFlutterEngine(FlutterEngine)不生效。 * 且用到的NewEngineIntentBuilder的构造器包外不可访问,因此无法重写相关方法而继续保持按内建方式获取Intent。继承一下将构造器外用 */ public static class MyNewEngineIntentBuilder extends NewEngineIntentBuilder { MyNewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) { super(activityClass); } } public static class MyCachedEngineIntentBuilder extends CachedEngineIntentBuilder{ protected MyCachedEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass, @NonNull String engineId) { super(activityClass, engineId); } }
} ```
使用 BridgeActivity 类,在启动 Flutter 页面的时候把 FlutterActivity 替换为 BridgeActivity:
startActivity( BridgeActivity .withCachedEngine("custom_engine_id") .build(MainActivity.this) );
在 Flutter 中如果需要调用原生系统的方法:
class FlutterRoute extends StatefulWidget { FlutterRouteState createState() { return FlutterRouteState(); } } class FlutterRouteState extends State<FlutterRoute> { static const channel = MethodChannel("TEST_CHANNEL_NAME"); Future<void> startNativeActivity() async{ await channel.invokeMethod("startNativeActivity"); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Flutter 页面"), ), body: Container( color: Colors.lightGreenAccent, child: Center( child: Column( children: <Widget>[ Text("Flutter 页面"), MaterialButton( color: Colors.redAccent, child: Text("跳转到原生 Activity"), onPressed: () { startNativeActivity(); }, ), ], ), ), ), ); } }
为什么不直接使用 FlutterActivity 呢?
原因看这篇文章:Add-to-app的MethodCallHandler不生效抛MissingPluginException解决
被这个问题折腾了好久,直接在 Flutter 工程中使用 MethodChannel 是可以正常获取 Android 端信息的,但是将 Flutter 作为一个 Module 添加到原有的 Android 项目就会出现异常,百度谷歌githubStackOverflow翻遍了,都是千篇一律的抄来抄去,直到发现了这篇文章。
Flutter 和 Android 之间的值传递
Flutter 官方提供了一种 Platform Channel 的方案,用于 Dart 和平台之间相互通信。
其核心原理:
- Flutter 应用通过 Platform Channel 将传递的数据编码成消息的形式,跨线程发送到该应用所在的宿主;
- 宿主接收到 Platform Channel 的消息后,调用相应平台的 API,也就是原生编程语言来执行相应方法;
- 执行完成后将结果数据通过同样方式原路返回给应用程序的 Flutter 部分。
整个过程的消息和响应是异步的,所以不会直接阻塞用户界面。
其调用流程如下图:
返回流程如下图:
Flutter 提供了三种 Channel:
- BasicMessageChannel:传递字符串和半结构化数据
- MethodChannel:方法调用
- EventChannel:数据流的通信
在 Android 端,通过调用 Result
的 success
和 error
方法来向 Flutter 调用端传递消息。
在 Fluutter 端,通过调用 MethodChannel 的 invokeMethod
方法来调用宿主端的方法。
上面的例子写的比较清楚了,这里例子就不列举了。
Flutter 的 MethodChannel 在使用过程中还有听过弊端的,就好比在原生界面向 Flutter 页面跳转的时,无法携带参数等问题,可能是我研究不够没找对方法,等我摸清楚了再回来补吧。